/** The Most Basic Ajax - PHP Error Debugger
 *
 * Formaldehyde JS aim is to make easy and automatic the integration
 * with formaldehyde.php file without affecting already existing code.
 * 
 * This file will wrap XMLHttpRequest instances in an unobtrusive way,
 * emulating native standard behaviors.
 * If the Ajax server call generated a status 500, Formaldehyde JS
 * will find automatically the best way to debug the error and
 * optional logs, if any.
 * 
 * All we need to do, is to include in our development environment
 * this file and before any other.
 * --------------------------------------------------------------
 *
 * Copyright (c) 2009 Andrea Giammarchi
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @category    Ajax
 * @author      Andrea Giammarchi
 * @copyright   2009 Andrea Giammarchi
 * @version     CVS: $Id: formaldehyde.js, v 1.02 2009/09/30 23:00:14 andrea.giammarchi Exp $
 * @license     http://www.opensource.org/licenses/mit-license.php
 * @project     http://code.google.com/p/formaldehyde/
 * @blog        http://webreflection.blogspot.com/
 */
 (function(window, ActiveXObject){
if(ActiveXObject = window.ActiveXObject)
    /**
     * native ActiveXObject wrapper, usually the choice due
     * to IE7 XMLHttpRequest inconsistency
     */
    window.ActiveXObject = function(servername, typename, location){
        return servername === "Microsoft.XMLHTTP" ?
            new XMLHttpRequest :
            new ActiveXObject(servername, typename, location)
        ;
    }
;

/** XMLHttpRequest Wrapper,
 * modified from IE6 vice-versa wrapper
 *
 * @project     http://code.google.com/p/vice-versa/
 */
function XMLHttpRequest(){
    var self = this;
    self._xhr = ActiveXObject ?
        // let's choose ActiveXObject over regular XMLHttpRequest
        new ActiveXObject("Microsoft.XMLHTTP") :
        new $XMLHttpRequest
    ;
    // set predefined onreadystatechange event
    self._xhr.onreadystatechange = function(){
        // sync the instance and fire Formaldehyde events if
        // readyState is 4 (complete)
        if(sync(self, 4 == self._xhr.readyState).onreadystatechange)
            // fire instance event if onreadystatechange is present
            self.onreadystatechange.call(null)
        ;
    };
};

/** XMLHttpRequest V1 Prototype
 */
XMLHttpRequest.prototype = {
    constructor:XMLHttpRequest,
    abort:function(){
        // avoid internal errors on abort
        this._xhr.onreadystatechange = anonymous;
        this._xhr.abort();
    },
    getAllResponseHeaders:function(){
        // IE throws errors if we try to access
        // headers before resyState is 4 (complete)
        if(this.readyState < 4 && ActiveXObject)
            throw new Error("asd")
        ;
        return  this._xhr.getAllResponseHeaders();
    },
    getResponseHeader:function(key){
        // IE throws errors if we try to access
        // headers before resyState is 4 (complete)
        if(this.readyState < 4 && ActiveXObject)
            throw new Error("asd")
        ;
        return this._xhr.getResponseHeader(key);
    },
    open:function(method, url, async, user, pass){
        // standard browsers do not fire redystatechange event
        // when the interaction is sync
        // this flag will help the send method
        this._async = !!async;
        this._xhr.open(method, url, async, user, pass);
    },
    send:function(value){
        this._xhr.send(value);
        // if this interaction was sync
        // and onreadystatechange has not been called
        if(!this._async && this.readyState != 4)
            // sync the instance and fire Formaldehyde events
            sync(this, true)
        ;
    },
    setRequestHeader:function(key, value){
        this._xhr.setRequestHeader(key, value);
    },
    // default properties value
    readyState:0,
    status:0,
    responseText:"",
    statusText:"",
    responseXML:null
};

/** ServerError extends Error
 * Emulate a real error in console
 * The good part is that the name of the
 * instance, ServerError, will be showed
 * plus in some case the object highlighted
 * (probably only FireBUG so far)
 */
function ServerError(e){
    for(var key in e)
        this[key] = e[key]
    ;
};
// necesary to highlight the object in FireBUG
(ServerError.prototype = new Error).name = "ServerError";

/** callback to show errors or logs
 * Try to figure out via horribles
 * try catch which console is usable, if any,
 * otehrwise fallback into an alert
 * after trying with an external page
 */
function show(responseText, error){
    // let's say I definitively trust formaldehyde_encode ...
    // and we are in development environment ...
    var o = Function("return " + responseText)();
    // if it is an error, create the proper instance
    if(error)
        o = new ServerError(o)
    ;
    try {
        // IE8 has quite meaningless log when an Error is passed there ...
        if(ActiveXObject)
            throw new Error;
        ;
        // FireBUG, Chrome, Safari
        error ? console.warn(o) : console.log(o);
    } catch(e) {
        try {
            // avoid IE8 ...
            if(ActiveXObject)
                throw e;
            ;
            // other with just log and not warn, if any
            console.log(o);
        } catch(e) {
            // create via recursion the error or the log schema
            responseText = "";
            (function loop(o, t, c){
                // indent ...
                c = new Array(t + 1).join("\t");
                for(var key in o)
                    (function write(key, value){
                        responseText += c + key + ": ";
                        if(typeof value === "object"){
                            // will never fail unless some desperate redefined the Array ...
                            // same sandbox
                            responseText += (value instanceof Array ? "Array" : "Object") + "\n";
                            loop(value, t + 1);
                        } else
                            responseText += value;
                        ;
                        responseText += "\n";
                    })(key, o[key], t)
                ;
            })(o, 0);
            try {
                // opera has an horrible postError
                // which does not manage objects
                // let's try it
                opera.postError(responseText);
            } catch(e) {
                try {
                    // last chance for useless IE8 console logger
                    console.log(responseText);
                } catch(e) {
                    try {
                        // let's try the open window way
                        // putting there the created schema
                        open("", "Formaldehyde", "width=500,height=280").document.write([
                            "<pre style='width:480px;hwight:260px;overflow:auto'>",
                            "<strong>Formaldehyde " + (error ? "ServerError" : "Log") + "</strong><hr />\n",
                            responseText,
                            "</pre>"
                        ].join(""));
                    } catch(e) {
                        // Formaldehyde is for Web Developers
                        // if this alert acts, we are doing wrong!
                        // anyway, could be useful for old browsers debug
                        alert(responseText);
                    }
                }
            }
        }
    };
};

/** XMLHttpRequest Sync - From Native To Wrap
 */
function sync(self, getResponseHeader){
    var flog;
    self.readyState = self._xhr.readyState;
    // IE does not allow property access
    // IE does not have set/get so this could be
    // a remote inconsistency
    if(getResponseHeader || !ActiveXObject){
        self.responseText = self._xhr.responseText;
        self.responseXML = self._xhr.responseXML;
    };
    // only if readyState is 4 or it was a sync request with other browsers
    if(getResponseHeader){
        self.statusText = self._xhr.statusText;
        if(500 == (self.status = self._xhr.status) && self._xhr.getResponseHeader("X-Formaldehyde") !== null)
            // here, is the magic trick
            show(self.responseText, true)
        ;
        if((flog = self._xhr.getResponseHeader("X-Formaldehyde-Log")) !== null && flog !== "[]")
            // while here, there's logged info, if any
            show(flog, false)
        ;
    };
    return self;
};

// we still need native XMLHttpRequest
var $XMLHttpRequest = window.XMLHttpRequest,
// plus an anonymous function to avoid problems
// with onreadystatechange (maybe null was enough)
    anonymous = function(){}
;

// only if the browser supports native XMLHttpRequest
if($XMLHttpRequest)
    // wrap them, otherwise ActiveXObject will take care
    // of "Ajax instances creations"
    window.XMLHttpRequest = XMLHttpRequest
;

// which global one?
})(this); // one